Skip to content

Report the Pareto k diagnostic for sis() and tis() - #411

Closed
avehtari wants to merge 2 commits into
masterfrom
is-pareto-k-diagnostic
Closed

avehtari wants to merge 2 commits into
masterfrom
is-pareto-k-diagnostic

Conversation

@avehtari

Copy link
Copy Markdown
Member

Fixes #227.

This PR was assisted by Claude

Summary

sis() and tis() set pareto_k to 0 for every observation, so pareto_k_values() reported the best possible diagnostic value, although loo had not computed any diagnostic. psis() reports Inf when the tail has fewer than 5 draws, so for few draws the three methods disagreed (Inf for psis(), 0 for the other two). With many draws they disagreed too: psis() returned the estimated k, and the other two still returned 0.

The Pareto k diagnostic depends only on the log ratios, because psis() estimates k before it smooths the tail. sis() and tis() now compute the same k as psis() from the same log ratios. The weights of all three methods do not change.

Change

  • A new internal function pareto_k_i() in R/psis.R fits the generalized Pareto distribution to the same tail as do_psis_i(), with the same cutoff and the same posterior::gpdfit() call, and skips the smoothing. It returns Inf in the cases where do_psis_i() does: a tail shorter than 5 draws, a constant tail, or a failed fit.
  • do_sis_i() and do_tis_i() use pareto_k_i(). tis() computes k from the log ratios before truncation.
  • do_importance_sampling() gives the "Not enough tail samples" warning for all three methods. Before, only psis() gave it.
  • sis() and tis() now give the high Pareto k warning when k is above the threshold, as psis() does.
  • The documentation of pareto_k and tail_len for sis() and tis() describes the diagnostic.
  • print() of a sis_loo or tis_loo object wrote "using sis_loo ." with a space before the period. I removed the space.

For the example in #227 (3 iterations of 2 chains), all three methods now return Inf for all 32 observations and give the same warnings. With 500 and 1000 draws of example_loglik_array(), the three methods return identical k values.

sis() and tis() now fit the generalized Pareto distribution once per observation, as psis() does, so they take a little more time.

Tests

tests/testthat/test_tisis.R:

  • New test psis, sis and tis report the same Pareto k diagnostic: identical k for example_loglik_array(). An observation with t-distributed log ratios (1 degree of freedom) gives k > 0.7 and the high-k warning for each method.
  • New test psis, sis and tis report Inf for too few draws: the Inconsistent Pareto k-values for SIS? #227 example gives Inf for all observations and both warnings for each method.
  • The existing value tests call sis() and tis() with 16 and 10 draws. They expected no warning; they now expect the tail-length warning, the same one psis() gives.
  • The print snapshot has the corrected spacing.

With the master R files, the new and changed tests give 16 failures. With this PR, devtools::test() with NOT_CRAN=true passes all 1283 tests and gives no warnings (R 4.6.1, testthat 3.3.2, Linux x86_64).

@codecov-commenter

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 73.91304% with 6 lines in your changes missing coverage. Please review.
✅ Project coverage is 92.33%. Comparing base (fd910fe) to head (503361b).

Files with missing lines Patch % Lines
R/psis.R 62.50% 6 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master     #411      +/-   ##
==========================================
- Coverage   92.48%   92.33%   -0.15%     
==========================================
  Files          31       31              
  Lines        3139     3157      +18     
==========================================
+ Hits         2903     2915      +12     
- Misses        236      242       +6     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@github-actions

Copy link
Copy Markdown
Contributor

This is how benchmark results would change (along with a 95% confidence interval in relative change) if 503361b is merged into master:

  • ✔️loo_function: 1.81s -> 1.82s [-0.21%, +0.52%]
  • ✔️loo_matrix: 1.58s -> 1.58s [-0.31%, +0.49%]
    Further explanation regarding interpretation and methodology can be found in the documentation.

@jgabry jgabry left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, this looks pretty good. I had Codex check that the k values from sis()/tis() are identical to psis() (example data and a simulated 4000×2000 matrix with one heavy-tailed column) and that the weights are unchanged. I made a few suggestions, mostly about side effects for users.

And one other thing: I think the cost is more than "a little". I asked Codex to check and with S = 4000 and N = 2000, sis() went from 0.38s to 1.66s and tis() from 0.63s to 1.60s, about the same as psis() (1.69s). That's probably fine for someone calling it directly, but loo_subsample(loo_approximation = "tis"/"sis") calls loo.function() with these methods and throws away the k values. With N = 5000, S = 4000 and 200 subsampled observations it went from 5.5s to 7.6s. Do you think that's fine? I'm not sure how much the slowdown would be for bigger examples, maybe a lot? Is it worth skipping the diagnostic in that case?

Comment thread R/importance_sampling.R
k_threshold <- ps_khat_threshold(S)
tail_len <- n_pareto(r_eff, S)
# all methods report the Pareto k diagnostic, which needs a long enough tail
throw_tail_length_warnings(tail_len)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With this change, a user calling sis() or tis() users the multi-column tail-length warning, which ends with "Skipping the following columns: 1, 2, ...". But nothing is skipped for these methods except the diagnostic, because the weights are computed as usual, so this could maybe be a confusing warning message? Maybe not a big deal. We could maybe reword the message in throw_tail_length_warnings() to something like e.g. "Pareto k is set to Inf for the following columns: ..."? I'm not sure it's worth changing that message though. Just pointing this out but not a big deal.

Comment thread R/sis.R
#' * `pareto_k`: Not used in `sis`, all set to 0.
#' * `pareto_k`: Estimates of the shape parameter \eqn{k} of the
#' generalized Pareto distribution, computed from the log ratios as in
#' [psis()]. `sis()` uses them only as a diagnostic.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sis() and tis() now warn using the PSIS threshold min(1 - 1/log10(S), 0.7) and pareto_k_table() calls everything below the threshold "good". But for plain IS don't the ratios already have infinite variance when k > 0.5? So e.g. with S = 1000 a k of 0.6 is "good" for sis() and gives no warning. Is the PSIS threshold what we want for sis/tis? Might be simplest to keep it but put in the docs that the threshold and the labels in pareto_k_table() are the PSIS ones? What do you think?

Comment thread R/tis.R
#' * `pareto_k`: Not used in `tis`, all set to 0.
#' * `pareto_k`: Estimates of the shape parameter \eqn{k} of the
#' generalized Pareto distribution, computed from the log ratios before
#' truncation as in [psis()]. `tis()` uses them only as a diagnostic.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same comment that I made in sis.R.

Comment thread R/print.R
cat(
"Computed from", dims[1], "posterior draws and",
dims[2], "log-likelihood terms using", class(x)[1], ".\n"
dims[2], "log-likelihood terms using", paste0(class(x)[1], ".\n")

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not about this line, but related: print.importance_sampling_loo() (line 57) still doesn't print the Pareto k table. So loo(x, is_method = "sis") will now warns "Some Pareto k diagnostic values are too high. See help('pareto-k-diagnostic')", but the printed output has no diagnostic. If k means something for sis/tis now, should we print pareto_k_table() like print.psis_loo() does? I'm not sure actually, and it doesn't necessarily need to be decided in this PR

Comment thread R/psis.R
#' @return Scalar Pareto k estimate. `Inf` if the tail is too short or constant,
#' or if the fit fails, as in `do_psis_i()`.
#'
pareto_k_i <- function(log_ratios_i, tail_len_i) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This repeats the tail selection, cutoff and constant-tail check from do_psis_i(). do_psis_i(log_ratios_i, tail_len_i)$pareto_k gives identical k (I had Codex check on 2000 columns) and would remove the duplicated code. Although the diagnostic step would take longer because of the smoothing. I'm not entirely sure which to prioritize, so I'm ok with keeping this as is or remove the duplicate code.

Comment thread R/psis.R
lw_sorted <- sort.int(log_ratios_i - max(log_ratios_i))
tail_ids <- seq(S - tail_len_i + 1, S)
lw_tail <- lw_sorted[tail_ids]
if (abs(max(lw_tail) - min(lw_tail)) < .Machine$double.eps / 100) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The constant-tail case isn't covered by the tests. test_psis.R:146 tests it for do_psis_i(), so we could we add the same case for sis()/tis()

@avehtari

Copy link
Copy Markdown
Member Author

Based on your comments, I think we should actually drop this and put NA for Prateo k for these

@avehtari
avehtari marked this pull request as draft October 21, 2026 17:52
@avehtari avehtari closed this Oct 21, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Inconsistent Pareto k-values for SIS?

3 participants